﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows;

namespace PetriNetSharp.Engine
{
	/// <summary>
	/// A logical place in a petri net
	/// </summary>
	public class Place : PetriObject
	{
		/// <summary>
		/// The default number of max tokens
		/// </summary>
		public const int DEFAULT_MAX_TOKENS = 100;

		/// <summary>
		/// Occurs when the starting tokens changes
		/// </summary>
		public event Action StartingTokensChanged;

		/// <summary>
		/// Occurs when the running tokens changes
		/// </summary>
		public event Action RunningTokensChanged;

		/// <summary>
		/// Occurs when the maximum number of tokens changes
		/// </summary>
		public event Action MaxTokensChanged;

		/// <summary>
		/// The number of tokens this place starts out
		/// with when the petri net is started.
		/// </summary>
		private int startingTokens;

		/// <summary>
		/// The number of tokens this place has while the petri net
		/// is running.
		/// </summary>
		private int runningTokens;

		/// <summary>
		/// The maximum number of tokens this place can have
		/// </summary>
		private int maxTokens;


		/// <summary>
		/// Create a new Place with zero tokens
		/// </summary>
		/// <param name="location">The location of this place</param>
		/// <param name="name">The name of this place</param>
		/// <param name="uid">The unique ID of this palce</param>
		public Place(Point location, string name, string uid)
			: base(name, location, uid)
		{
			startingTokens = 0;
			runningTokens = 0;
			maxTokens = DEFAULT_MAX_TOKENS;
		}

		/// <summary>
		/// Create a new Place
		/// </summary>
		/// <param name="location">The location of this place</param>
		/// <param name="name">The name of this place</param>
		/// <param name="uid">The unique ID of this palce</param>
		/// <param name="startingTokens">The number of tokens to start with</param>
		/// <param name="maxTokens">The maximum number of tokens</param>
		/// <preconditions>
		/// startingTokens must be less than or equal to max tokens
		/// starting tokens must be greater than or equal to zero
		/// maxTokens must be greater than 0
		/// </preconditions>
		public Place(Point location, string name, string uid, int startingTokens, int maxTokens)
			: base(name, location, uid)
		{
			#region preconditions
			Debug.Assert(startingTokens >= 0, "Place constructor's startingTokens less than zero",
				"Place was constructed when the parameter startingTokens was less than zero");
			Debug.Assert(startingTokens <= maxTokens, 
				"Place constructor's startingTokens was greater than maxTokens",
				"Place was constructed when the parameter startingTokens was greater than the parameter maxTokens");
			Debug.Assert(maxTokens > 0, "Place constructor's maxTokens was not greater than zero",
				"Place was constructed when the parameter maxTokens was not greater than zero");
			#endregion
			
			this.startingTokens = startingTokens;
			runningTokens = 0;
			this.maxTokens = maxTokens;
		}


		/// <summary>
		/// Get whether this Place is running
		/// </summary>
		public override bool IsRunning
		{
			get
			{
				return base.IsRunning;
			}
			protected set
			{
				if (IsRunning != value)
				{
					//If stopping the runn set running tokens to 0
					//else load the running tokens
					if (IsRunning)
						RunningTokens = 0;
					else
						RunningTokens = StartingTokens;
					base.IsRunning = value;
				}
			}
		}

		/// <summary>
		/// Get or set this starting tokens for this place
		/// </summary>
		/// <preconditions>
		/// On set: Value must be greater than 0
		/// </preconditions>
		public int StartingTokens
		{
			get { return startingTokens; }
			set
			{
				#region preconditions 
				Debug.Assert(value >= 0);
				#endregion

				startingTokens = value;
				RaisePropertyChanged("StartingTokens");

				if (StartingTokensChanged != null)
					StartingTokensChanged();
			}
		}

		/// <summary>
		/// Get or set the number of tokens this place has 
		/// while the petri net is running.
		/// </summary>
		/// <preconditions>
		/// On set: value must be greater than 0
		/// </preconditions>
		public int RunningTokens
		{
			get { return runningTokens; }
			set
			{
				#region preconditions
				Debug.Assert(value >= 0);
				#endregion

				runningTokens = value;
				RaisePropertyChanged("RunningTokens");

				if (RunningTokensChanged != null)
					RunningTokensChanged();
			}
		}

		/// <summary>
		/// Get or set the Max Tokens
		/// </summary>
		/// <remarks>
		/// If new value if less than starting tokens or running tokens
		/// those values will be set to new MaxTokens value
		/// </remarks>
		/// <preconditions>
		/// On set: value must be greater than 0
		/// </preconditions>
		public int MaxTokens
		{
			get { return maxTokens; }
			set
			{
				#region preconditions
				Debug.Assert(value >= 0, "MaxTokens set with value not greater than zero",
					String.Format("Place: {0} had MaxTokens set with invalid value: {1}", ToString(), value));
				#endregion

				maxTokens = value;

				if(StartingTokens > MaxTokens)
					StartingTokens = MaxTokens;

				if(RunningTokens > MaxTokens)
					RunningTokens = MaxTokens;

				RaisePropertyChanged("MaxTokens");

				if (MaxTokensChanged != null)
					MaxTokensChanged();

			}
		}

		/// <summary>
		/// Get the current number of tokens
		/// </summary>
		/// <remarks>
		/// When running this will get the running tokens, when not
		/// running will get the starting tokens
		/// </remarks>
		public int CurrentTokens
		{
			get
			{
				if (IsRunning)
					return RunningTokens;
				else
					return StartingTokens;
			}
		}

		/// <summary>
		/// Get whether this Place can give the passed number of tokens
		/// </summary>
		/// <param name="numTokens">The number of tokens to check against</param>
		public Boolean CanGiveTokens(int numTokens)
		{ 
			return RunningTokens >= numTokens;
		}

		/// <summary>
		/// Get whether this Place can accept the passed number of tokens
		/// </summary>
		/// <param name="numTokens">The number of tokens to check against</param>
		public Boolean CanTakeTokens(int numTokens)
		{ 
			return RunningTokens + numTokens <= MaxTokens;
		}


		/// <summary>
		/// This Place takes one token
		/// </summary>
		/// <param name="numTokens">The number of tokens to take</param>
		/// <preconditions>
		/// This Place can take the number of tokens
		/// IsRunning must be true
		/// numTokens must be greater than 0
		/// </preconditions>
		public void TakeTokens(int numTokens)
		{
			#region preconditions
			Debug.Assert(numTokens > 0,
				String.Format("Place: {0} cannot take a non positive amount of tokens", Name),
				String.Format("Place {0} cann had TakeTokens called with parameter numTokens value was: {1}", Name, numTokens));
			Debug.Assert(CanTakeTokens(numTokens), 
				String.Format("Place: {0} cannot take {1} tokens", Name, numTokens), 
				String.Format("Place: {0}, had TakeTokens() called when CanTakeTokens() was false", Name));
			Debug.Assert(IsRunning,
				String.Format("Place: {0} tried to take tokens when it was not running", Name),
				String.Format("Place: {0} had TakeTokens() called when IsRunning is false", Name));
			#endregion

			RunningTokens += numTokens;
		}

		/// <summary>
		/// This Place gives one token
		/// </summary>
		/// <param name="numTokens">The number of tokens to give</param>
		/// <preconditions>
		/// This Place can give the number of tokens
		/// IsRunning must be true
		/// numTokens must be greater than zero
		/// </preconditions>
		public void GiveTokens(int numTokens)
		{
			#region preconditions
			Debug.Assert(numTokens > 0,
				String.Format("Place: {0} GiveTokens() was called with a non postiive numTokens", Name),
				String.Format("Place: {0} GiveTokens() was called with parameter numTokens having a value of: {1}", Name, numTokens));
			Debug.Assert(CanGiveTokens(numTokens), 
				String.Format("Place: {0} cannot give {1} tokens", Name, numTokens),
				String.Format("Place: {0}, had GiveTokens() called when CanGiveTokens() was false", Name));
			Debug.Assert(IsRunning,
				String.Format("Place: {0} tried to give a token when it was not running", Name),
				String.Format("Place: {0} had GiveToken called when IsRunning is false", Name));
			#endregion

			RunningTokens -= numTokens;
		}

		/// <summary>
		/// Reload the starting tokens as the running tokens, 
		/// essentially reseting the token count for this Place
		/// </summary>
		/// <preconditions>
		/// IsRunnings must be true
		/// </preconditions>
		public void ResetTokens()
		{
			#region preconditions
			Debug.Assert(IsRunning, "ResetTokens called when IsRunning is false",
				"ReseTokens() in Place was called when the Place was not currently running");
			#endregion

			RunningTokens = StartingTokens;
		}

		#region Object method overrides

		/// <summary>
		/// Determines whether the passed object is equal to this Place
		/// </summary>
		/// <param name="obj">The object to check</param>
		/// <returns>True if they are equal, false if not</returns>
		public override bool Equals(object obj)
		{
			if ( obj is Place )
				return base.Equals(obj);

			return false;
		}

		/// <summary>
		/// Serves as a hash function for this Place
		/// </summary>
		/// <returns>The hash code for this Place</returns>
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		#endregion

	}
}
